package org.jeecg.modules.ncmeapis.network.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import ncme.digital.hszy.common.utils.RestTemplateUtil;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.modules.ncmeapis.network.entities.entity.NetworkPath;
import org.jeecg.modules.ncmeapis.network.entities.entity.vo.PathWithPerm;
import org.jeecg.modules.ncmeapis.network.enums.FileSource;
import org.jeecg.modules.ncmeapis.network.enums.PathPermCode;
import org.jeecg.modules.ncmeapis.network.enums.PathType;
import org.jeecg.modules.ncmeapis.network.service.INetworkPathService;
import org.jeecg.modules.ncmeapis.network.service.INetworkPermissionService;
import org.jeecg.modules.ncmeapis.network.service.INetworkService;
import org.jeecg.modules.ncmeapis.network.service.IdentityService;
import org.jeecg.modules.system.controller.CommonController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Service
@Transactional
public class NetworkServiceImpl implements INetworkService {

    @Value(value = "${jeecg.path.upload}")
    private String uploadpath;
    @Autowired
    private INetworkPathService networkPathService;
    @Autowired
    private IdentityService identityService;
    @Autowired
    private INetworkPermissionService networkPermissionService;
    @Autowired
    private CommonController commonController;

    @Override
    public List<PathWithPerm> list(String pid, boolean showFile) {
        NetworkPath parentPath;
        boolean isRoot = false;
        //如果pid为空,则获取depth最小的目录
        if (StringUtils.isEmpty(pid)) {
            List<NetworkPath> paths = Db.lambdaQuery(NetworkPath.class).eq(NetworkPath::getType, PathType.FOLDER).orderByAsc(NetworkPath::getDepth).last("limit 1").list();
            if (paths.size() > 0) parentPath = paths.get(0);
            else throw new RuntimeException("根目录不存在,请联系管理员");
        } else {
            //校验目录是否存在,校验是否是文件夹
            isRoot = pid.equals(NetworkPathServiceImpl.ROOT_PID_VALUE);
            parentPath = Db.lambdaQuery(NetworkPath.class).eq(isRoot ? NetworkPath::getPid : NetworkPath::getId, pid).one();
            if (parentPath == null || !parentPath.getType().equals(PathType.FOLDER)) {
                throw new RuntimeException("目录不存在或不是文件夹");
            }
            //校验文件或文件夹的权限
            List<NetworkPath> pathsWithoutPerm = networkPermissionService.checkThisAndChildrenHasPerm(parentPath, identityService.getUserId(), PathPermCode.VISIBLE, PathPermCode.PREVIEW);
            if (pathsWithoutPerm.size() > 0) throw new RuntimeException("您没有权限预览该目录");
        }
        //获取指定目录下的目录
        List<NetworkPath> list = networkPathService.list(new LambdaQueryWrapper<NetworkPath>().eq(NetworkPath::getPid, isRoot ? 0 : parentPath.getId()));
        //获取用户分配了权限的目录（包括从父级目录继承下来的权限）
        List<PathWithPerm> pathWithPerms = networkPermissionService.getPathsWithPermsByUserId(list, identityService.getUserId());
        pathWithPerms.removeIf(pathWithPerm -> !pathWithPerm.has(PathPermCode.VISIBLE));
        //判断是否不显示文件
        if (!showFile) pathWithPerms.removeIf(pathWithPerm -> pathWithPerm.getType().equals(PathType.FILE));
        return pathWithPerms;

    }

    @Override
    public File getLocalDirectory(String fileId) {
        //校验文件或文件夹是否存在
        NetworkPath networkPath = networkPermissionService.checkPathExist(fileId);
        networkPermissionService.checkPathType(networkPath, PathType.FOLDER, "该目录不是文件夹");
        if (networkPath.getFileSource().equals(FileSource.EXTERNAL)) throw new RuntimeException("该目录不是本地目录");
        String filePath;
        if (networkPath.getFileSource().equals(FileSource.jEECG)) {
            filePath = uploadpath + "/temp/" + networkPath.getName();
        } else if (networkPath.getFileSource().equals(FileSource.JEECG_API)) {
            filePath = uploadpath + networkPath.getPath();
        } else throw new RuntimeException("未知的文件来源");
        return new File(filePath);
    }

    @Override
    public File getLocalFile(String fileId) {
        //校验文件或文件夹是否存在
        NetworkPath networkPath = networkPermissionService.checkPathExist(fileId);
        networkPermissionService.checkPathType(networkPath, PathType.FILE, "该目录不是文件");
        if (networkPath.getFileSource().equals(FileSource.EXTERNAL)) throw new RuntimeException("该文件不是本地文件");
        String filePath;
        if (networkPath.getFileSource().equals(FileSource.jEECG)) {
            filePath = uploadpath + "/temp/" + networkPath.getName();
        } else if (networkPath.getFileSource().equals(FileSource.JEECG_API)) {
            filePath = uploadpath + networkPath.getPath();
        } else throw new RuntimeException("未知的文件来源");
        File file = new File(filePath);
        if (!file.exists()) throw new RuntimeException("目录与本地文件不匹配，本地文件不存在！");
        return file;
    }

    @Override
    public void preview(String fileId) {
        //校验文件或文件夹是否存在
        NetworkPath networkPath = networkPermissionService.checkPathExist(fileId);
        networkPermissionService.checkPathType(networkPath, PathType.FILE, "该目录不是文件");
        //校验文件或文件夹的权限
        List<NetworkPath> pathsWithoutPerm = networkPermissionService.checkPathsHasPerm(Collections.singletonList(networkPath), identityService.getUserId(), PathPermCode.VISIBLE, PathPermCode.PREVIEW);
        if (pathsWithoutPerm.size() > 0) throw new RuntimeException("您没有权限预览该文件或文件夹");
    }

    @Override
    public ResponseEntity<?> download(String pathId) {
        //校验文件或文件夹是否存在
        NetworkPath networkPath = networkPermissionService.checkPathExist(pathId);
        //校验文件或文件夹的权限
        List<NetworkPath> pathsWithoutPerm = networkPermissionService.checkThisAndChildrenHasPerm(networkPath, identityService.getUserId(), PathPermCode.VISIBLE, PathPermCode.DOWNLOAD);
        if (pathsWithoutPerm.size() > 0) throw new RuntimeException("您没有权限下载该文件或文件夹");
        else {
            try {
                if (!networkPath.getFileSource().equals(FileSource.EXTERNAL)) {
                    String filePath;
                    if (networkPath.getFileSource().equals(FileSource.jEECG)) {
                        filePath = uploadpath + "/temp/" + networkPath.getName();
                    } else if (networkPath.getFileSource().equals(FileSource.JEECG_API)) {
                        filePath = uploadpath + networkPath.getPath();
                    } else throw new RuntimeException("未知的文件来源");
                    Path path = new File(filePath).toPath();
                    // 检查文件是否存在
                    if (!Files.exists(path)) {
                        //如果没找到，可能是通过jeecg前端上传的文件，需要去temp目录下直接找
                        path = new File(uploadpath + "/temp/" + networkPath.getName()).toPath();
                        if (!Files.exists(path))
                            throw new RuntimeException("文件不存在");
                    }
                    // 获取文件大小
                    long fileSize = Files.size(path);
                    // 创建Resource对象
                    Resource resource = new FileSystemResource(path.toFile());
                    // 设置HTTP响应头
                    HttpHeaders headers = new HttpHeaders();
                    headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + networkPath.getName() + "\"");
                    headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
                    headers.setContentLength(fileSize); // 设置文件大小
                    // 返回文件内容作为响应体
                    return ResponseEntity.ok().headers(headers).body(resource);
                } else {
                    //TODO 来自外部文件
                    String uri = networkPath.getPath();
                    RestTemplateUtil restTemplateUtil = new RestTemplateUtil();
                    return restTemplateUtil.getDownload(uri);
                }
            } catch (Exception e) {
                throw new RuntimeException("下载失败，" + e.getMessage());
            }
        }
    }

    @Override
    public NetworkPath edit(String pathId) {
        //校验文件或文件夹是否存在
        NetworkPath networkPath = networkPermissionService.checkPathExist(pathId);
        //校验文件或文件夹的权限
        List<NetworkPath> pathsWithoutPerm = networkPermissionService.checkPathsHasPerm(Collections.singletonList(networkPath), identityService.getUserId(), PathPermCode.VISIBLE, PathPermCode.EDIT);
        if (pathsWithoutPerm.size() > 0) throw new RuntimeException("您没有权限编辑该文件或文件夹");
        return networkPath;
    }

    @Override
    public NetworkPath rename(String pathId, String newName) {
        //校验文件或文件夹是否存在
        NetworkPath networkPath = networkPermissionService.checkPathExist(pathId);
        //校验文件或文件夹的权限
        List<NetworkPath> pathsWithoutPerm = networkPermissionService.checkPathsHasPerm(Collections.singletonList(networkPath), identityService.getUserId(), PathPermCode.VISIBLE, PathPermCode.EDIT);
        if (pathsWithoutPerm.size() > 0) throw new RuntimeException("您没有权限编辑该文件或文件夹");
        else {
            //文件名不能为空,且不能包含特殊字符
            if (newName == null || newName.matches(".*[\\\\/:*?\"<>|].*")) {
                throw new RuntimeException("文件名不能为空,且不能包含特殊字符");
            }
            //修改path
            NetworkPath parent = networkPathService.getById(networkPath.getPid());
            networkPath.setName(newName);
            networkPath.setPath(parent.getPath() + (parent.getPath().endsWith("/") ? "" : "/") + networkPath.getName());
            networkPathService.updateNetworkPath(networkPath);
            //修改文件夹名后，需要把所有子文件和文件夹的path也修改
            if (PathType.FOLDER.equals(networkPath.getType())) {
                List<NetworkPath> childList = updateChildInfo(networkPath);
                networkPathService.updateBatchById(childList);
            }
            return networkPath;
        }
    }

    @Override
    public NetworkPath move(String pathId, String targetId) {
        //校验待移动文件以及目标文件夹是否存在,是否是文件夹
        NetworkPath networkPath = networkPermissionService.checkPathExist(pathId);
        NetworkPath target = networkPermissionService.checkPathExist(targetId, "目标目录不存在！");
        networkPermissionService.checkPathType(target, PathType.FOLDER, "目标目录不是文件夹");
        //校验该目录及子目录的权限
        List<NetworkPath> pathsWithoutPerm = networkPermissionService.checkThisAndChildrenHasPerm(networkPath, identityService.getUserId(), PathPermCode.VISIBLE, PathPermCode.MOVE);
        if (pathsWithoutPerm.size() > 0) {
            throw new RuntimeException("您没有权限移动以下文件或文件夹：" + pathsWithoutPerm.stream().map(NetworkPath::getName).reduce((a, b) -> a + "," + b).orElse(""));
        } else {
            //移动文件夹时，不能移动到自己的子文件夹下
            String path = target.getPath();
            if (PathType.FOLDER.equals(networkPath.getType())) {
                if (path.startsWith(networkPath.getPath())) throw new RuntimeException("不能移动到自己的子文件夹下！");
            }
            //修改path/层级/父级id
            networkPath.setPath(path + (path.endsWith("/") ? "" : "/") + networkPath.getName());
            networkPath.setPid(target.getId());
            networkPath.setDepth(target.getDepth() + 1);
            networkPathService.updateNetworkPath(networkPath);
            //修改父级文件夹是否有子节点
            target.setHasChild(INetworkPathService.HASCHILD);
            networkPathService.updateNetworkPath(target);
            //修改文件夹名后，需要把所有子文件和文件夹的path/层级也修改
            if (PathType.FOLDER.equals(networkPath.getType())) {
                List<NetworkPath> childList = updateChildInfo(networkPath);
                networkPathService.updateBatchById(childList);
            }
            return networkPath;
        }
    }

    @Override
    public NetworkPath mkdir(String dirName, String targetId) {
        //校验文件夹是否已存在
        checkNameRepeats(targetId, dirName, PathType.FOLDER);
        //校验目标文件夹是否存在
        NetworkPath targetPath = networkPermissionService.checkPathExist(targetId, "目标目录不存在！");
        networkPermissionService.checkPathType(targetPath, PathType.FOLDER, "目标目录不是文件夹");
        //校验目标文件夹的权限
        List<NetworkPath> pathsWithoutPerm = networkPermissionService.checkThisAndChildrenHasPerm(targetPath, identityService.getUserId(), PathPermCode.VISIBLE, PathPermCode.MKDIR);
        if (pathsWithoutPerm.size() > 0) throw new RuntimeException("您没有权限在该目录新建文件夹");
        else {
            //创建文件夹
            NetworkPath networkPath = new NetworkPath();
            networkPath.setName(dirName);
            networkPath.setPid(targetId);
            networkPath.setType(PathType.FOLDER);
            //拼接path
            String path = targetPath.getPath() + (targetPath.getPath().endsWith("/") ? "" : "/") + networkPath.getName();
            networkPath.setPath(path);
            //计算层级
            networkPath.setDepth(targetPath.getDepth() + 1);
            networkPathService.addNetworkPath(networkPath);
            return networkPath;
        }
    }

    @Override
    public NetworkPath mkdirIfNotExist(String dirName, String targetId) {
        NetworkPath networkPath = getByName(targetId, dirName, PathType.FOLDER);
        if (networkPath != null) return networkPath;
        else return mkdir(dirName, targetId);
    }

    @Override
    public NetworkPath upload(String targetPathId, String fileUrl, String fileSource) {
        String fileName = fileUrl.substring(fileUrl.lastIndexOf("/") + 1);
        checkNameRepeats(targetPathId, fileName, PathType.FILE);
        //校验目标文件夹是否存在
        NetworkPath targetPath = networkPermissionService.checkPathExist(targetPathId, "目标目录不存在！");
        networkPermissionService.checkPathType(targetPath, PathType.FOLDER, "目标目录不是文件夹");
        //校验目标文件夹的权限
        List<NetworkPath> pathsWithoutPerm = networkPermissionService.checkThisAndChildrenHasPerm(targetPath, identityService.getUserId(), PathPermCode.VISIBLE, PathPermCode.UPLOAD);
        if (pathsWithoutPerm.size() > 0) throw new RuntimeException("您没有权限上传到该文件夹");
        else {
            NetworkPath networkPath = new NetworkPath()
                    .setPid(targetPathId)
                    .setFileUrl(fileUrl)
                    .setType(PathType.FILE)
                    .setFileSource(fileSource)
                    .setName(fileName);
            //拼接path
            String path = targetPath.getPath() + (targetPath.getPath().endsWith("/") ? "" : "/") + networkPath.getName();
            networkPath.setPath(path);
            //计算层级
            networkPath.setDepth(targetPath.getDepth() + 1);
            networkPathService.addNetworkPath(networkPath);
            return networkPath;
        }
    }

    @Override
    public void delete(String pathId) {
        NetworkPath networkPath = networkPathService.getById(pathId);
        List<NetworkPath> pathsWithoutPerm = networkPermissionService.checkThisAndChildrenHasPerm(networkPath, identityService.getUserId(), PathPermCode.VISIBLE, PathPermCode.DELETE);
        if (pathsWithoutPerm.size() > 0) {
            throw new RuntimeException("您没有权限删除以下文件或文件夹：" + pathsWithoutPerm.stream().map(NetworkPath::getName).reduce((a, b) -> a + "," + b).orElse(""));
        } else {
            if (networkPath.getPath().equals("/")) throw new RuntimeException("根目录不能删除");
            //删除当前文件夹和所有子文件和文件夹
            networkPathService.removeById(pathId);
            networkPathService.remove(new LambdaQueryWrapper<NetworkPath>().likeRight(NetworkPath::getPath, networkPath.getPath() + "/"));
        }
    }

    @Override
    public void checkNameRepeats(String pathId, String pathName, String pathType) {
        //校验文件夹或文件名称是否重复,若重复,抛运行时异常
        NetworkPath existPath = getByName(pathId, pathName, pathType);
        if (existPath != null) throw new RuntimeException("该目录下已存在同名文件或文件夹");
    }

    private NetworkPath getByName(String pathId, String pathName, String pathType) {
        return networkPathService.getOne(new LambdaQueryWrapper<NetworkPath>()
                        .eq(NetworkPath::getPid, pathId)
                        .eq(NetworkPath::getName, pathName)
                        .eq(NetworkPath::getType, pathType),
                false);
    }

    /**
     * 修改子文件或文件夹的 path
     */
    private List<NetworkPath> updateChildInfo(NetworkPath networkPath) {
        List<NetworkPath> result = new ArrayList<>();
        new LambdaQueryChainWrapper<>(NetworkPath.class).eq(NetworkPath::getPid, networkPath.getId()).list().forEach(child -> {
            child.setPath(networkPath.getPath() + "/" + child.getName());
            //如果是移动文件夹调用,则需要修改层级
            child.setDepth(networkPath.getDepth() + 1);
            result.add(child);
            result.addAll(updateChildInfo(child));
        });
        return result;
    }

}
